package com.googlecode.horncomb.web.spring.tags;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.jsp.JspException;

import net.sf.json.JSONObject;

import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.support.BindStatus;
import org.springframework.web.servlet.tags.form.AbstractFormTag;
import org.springframework.web.servlet.tags.form.TagWriter;

import com.googlecode.horncomb.util.lang.BeanUtils;
import com.googlecode.horncomb.web.util.TagUtils;

public class SecurityInputTag extends DynamicInputTag {
	private static final long serialVersionUID = 4087620791828899427L;

	public static final String PAGE_PERMISSION_KEY = "pagePermission";

	private static final Double defaultPermission = 9999d;
	private String permission = null;
	private String required = "false";
	private AbstractFormTag innerTag = null;
	/**
	 * Marker object for items that have been specified but resolve to null.
	 * Allows to differentiate between 'set but null' and 'not set at all'.
	 */
	private static final Object EMPTY = new Object();

	private static HashSet<String> outputOnRead = new HashSet<String>();
	{
		outputOnRead.add("radio");
		outputOnRead.add("radioGroup");
		outputOnRead.add("checkbox");
		outputOnRead.add("checkboxGroup");
	}

	@SuppressWarnings("unchecked")
	@Override
	protected int writeTagContent(TagWriter tagWriter) throws JspException {
		double pms = this.getPermission();
		// 设置必填
		String jsonStr = "";
		String css = this.resolveCssClass();
		css = css == null ? "" : css;
		int begin = css.indexOf('{');
		int end = css.lastIndexOf('}') + 1;
		if (StringUtils.hasText(css) && begin > -1 && end > 0) {
			jsonStr = css.substring(begin, end);
			css = css.substring(0, begin) + " " + css.substring(end);
		}
		if (this.getRequired()) {
			JSONObject jsonRoot = null;
			if (StringUtils.hasText(jsonStr)) {
				jsonRoot = JSONObject.fromObject(jsonStr);
			} else {
				jsonRoot = new JSONObject();
			}
			JSONObject validate = (JSONObject) jsonRoot.get("validate");
			if (validate == null || validate.isNullObject()) {
				jsonRoot.accumulate("validate", new JSONObject());
				validate = (JSONObject) jsonRoot.get("validate");
			}
			validate.remove("required");
			validate.accumulate("required", true);
			this.setCssClass(css + jsonRoot.toString().replace("\"", "'"));
		}

		if (pms > 0) {
			return super.writeTagContent(tagWriter);
		}

		// 可显示的输出
		if (outputOnRead.contains(this.getType())) {
			if (pms <= 0) {
				// radio/checkbox
				this.setDisabled(Boolean.TRUE.toString());// 只读状态

				// 输出隐藏域
				Object boundValue = this.getBoundValue();
				Collection valClt = null;
				if (boundValue.getClass().isArray()) {
					valClt = Arrays.asList(boundValue);
				} else if (boundValue instanceof Collection) {
					valClt = (Collection) boundValue;
				} else if (boundValue instanceof Map) {
					valClt = ((Map) boundValue).keySet();
				}
				if (valClt != null) {
					for (Object val : valClt) {
						this.writeHiddenField(tagWriter, val);
					}
				} else {
					this.writeHiddenField(tagWriter, boundValue);
				}
			}
			if (pms == 0) {
				return super.writeTagContent(tagWriter);
			} else {
				return SKIP_BODY;
			}
		} else {
			if (pms <= 0) {
				// 输出隐藏域
				this.writeHiddenField(tagWriter, this.getBoundValue());
			}
			if (pms == 0) {
				// 显示SPAN块
				SpanTag spanTag = new SpanTag();
				this.innerTag = spanTag;
				this.innerTag.setPageContext(this.pageContext);
				BeanUtils.copyPropertiesAsPossible(this, this.innerTag);
				spanTag.setCssClass(css);
				spanTag.doStartTag();
				String label = "";
				if ("select".equals(this.getType())) {
					Object items = this.getItems();
					if (items != null) {
						label = getSelectLabel(this.getItems());
					}
				} else {
					label = this.getDisplayString(this.getBoundValue(), this
							.getPropertyEditor());
				}
				if (StringUtils.hasText(label)) {
					spanTag.getTagWriter().appendValue(label);
					return SKIP_BODY;
				} else {
					return EVAL_BODY_INCLUDE;
				}
			}
		}

		return SKIP_BODY;
	}

	@SuppressWarnings("unchecked")
	private String getSelectLabel(Object items) throws JspException {
		String valueProperty = (getItemValue() != null ? ObjectUtils
				.getDisplayString(this.evaluate("itemValue", getItemValue()))
				: null);
		String labelProperty = (getItemLabel() != null ? ObjectUtils
				.getDisplayString(this.evaluate("itemLabel", getItemLabel()))
				: null);
		String result = "";
		if (items != null) {
			// Items specified, but might still be empty...
			if (items != EMPTY) {
				Object itemsObject = (items instanceof String ? evaluate(
						"items", (String) items) : items);
				if (itemsObject != null) {
					if (items.getClass().isArray()) {
						result = this.getSelectLabelForCollection(Arrays
								.asList(items), valueProperty, labelProperty);
					} else if (items instanceof Collection) {
						result = this.getSelectLabelForCollection(
								(Collection) items, valueProperty,
								labelProperty);
					} else if (items instanceof Map) {
						result = this.getSelectLabelForMap((Map) items,
								valueProperty, labelProperty);
					} else {
						throw new JspException("Type ["
								+ items.getClass().getName()
								+ "] is not valid for option items");
					}
				}
			}
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	private String getSelectLabelForMap(Map optionMap, String labelProperty,
			String valueProperty) throws JspException {
		for (Iterator it = optionMap.entrySet().iterator(); it.hasNext();) {
			Map.Entry entry = (Map.Entry) it.next();
			String selectedLable = isSelected(entry, entry.getValue(), entry
					.getKey());
			if (StringUtils.hasText(selectedLable)) {
				return selectedLable;
			}
		}
		return "";
	}

	@SuppressWarnings("unchecked")
	private String getSelectLabelForCollection(Collection optionCollection,
			String valueProperty, String labelProperty) throws JspException {
		for (Iterator it = optionCollection.iterator(); it.hasNext();) {
			Object item = it.next();
			BeanWrapper wrapper = new BeanWrapperImpl(item);
			Object value = (valueProperty != null ? wrapper
					.getPropertyValue(valueProperty) : item);
			Object label = (labelProperty != null ? wrapper
					.getPropertyValue(labelProperty) : item);
			String selectedLable = isSelected(item, value, label);
			if (StringUtils.hasText(selectedLable)) {
				return selectedLable;
			}
		}
		return "";
	}

	private String isSelected(Object item, Object label, Object value)
			throws JspException {
		try {
			Class<?> cmpCls = ClassUtils
					.forName("org.springframework.web.servlet.tags.form.SelectedValueComparator");
			Method method = cmpCls.getMethod("isSelected", new Class[] {
					BindStatus.class, Object.class });
			if (cmpCls.getModifiers() != Modifier.PUBLIC
					|| method.getModifiers() != Modifier.PUBLIC) {
				method.setAccessible(true);
			}
			boolean selected = (Boolean) method.invoke(null, new Object[] {
					this.getBindStatus(), value })
					|| (Boolean) method.invoke(null, new Object[] {
							this.getBindStatus(), item });
			if (selected) {
				return this.getDisplayString(label);
			} else {
				return "";
			}
		} catch (Exception e) {
			throw new JspException(e);
		}
	}

	private void writeHiddenField(TagWriter tagWriter, Object value)
			throws JspException {
		tagWriter.startTag("input");
		writeDefaultAttributes(tagWriter);
		tagWriter.writeAttribute("type", "hidden");
		tagWriter.writeAttribute("value", getDisplayString(value,
				getPropertyEditor()));
		tagWriter.endTag();
	}

	/**
	 * @param permission
	 *            the permission to set
	 */
	public void setPermission(String permission) throws NumberFormatException,
			JspException {
		this.permission = permission;
	}

	/**
	 * @return
	 * @throws NumberFormatException
	 * @throws JspException
	 */
	protected Double getPermission() throws NumberFormatException, JspException {
		String pms = this.getDisplayString(this.evaluate("permission",
				this.permission));
		if (!StringUtils.hasText(pms)) {
			Object pagePrmsObj = this.pageContext
					.getAttribute(PAGE_PERMISSION_KEY);
			if (pagePrmsObj != null) {
				pms = pagePrmsObj.toString();
			}
		}
		if (!StringUtils.hasText(pms)) {
			return defaultPermission;
		} else {
			return Double.parseDouble(pms);
		}
	}

	/**
	 * @param required
	 *            the required to set
	 */
	public void setRequired(String required) {
		this.required = required;
	}

	protected boolean getRequired() throws JspException {
		return TagUtils.getBooleanValue("required", this.required, pageContext);
	}
//
//	protected String getName() throws JspException {
//		if (this.getPermission() > 0) {
//			return getCompletePath();
//		} else {
//			return "";
//		}
//	}

	@Override
	public int doAfterBody() throws JspException {
		if (this.innerTag != null) {
			return this.innerTag.doAfterBody();
		} else {
			return super.doAfterBody();
		}
	}

	@Override
	public int doEndTag() throws JspException {
		if (this.innerTag != null) {
			return this.innerTag.doEndTag();
		} else {
			return super.doEndTag();
		}
	}

	@Override
	public void doFinally() {
		if (this.innerTag != null) {
			this.innerTag.doFinally();
		} else {
			super.doFinally();
		}
		this.innerTag = null;
	}
}
